<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class PHLARE_Model extends Model
{
	// Class properties
	protected $_fields = array();
	protected $_versions = array();
	protected $db_table = '';
	protected $_tabs = array();
	public $id = null;
	public $parent_id = null;
	public $left_val = null;
	public $right_val = null;
	public $stage_state = null;
	
	
	/**
	 * __construct:
	 * Initialization tasks.
	 * 
	 * @return null
	 */
	public function __construct()
	{
		// run the parent constructor... uses PHP 4 style of naming the constructor the name of the class.
		parent::Model();
		
		// set the db table name
		// Note that the prefix is automatically added if you are using active record queries. Which we are... mostly.
		$this->db_table = strtolower($this->get_class());
	} // end __construct
	
	
	public function get_class()
	{
		return get_class($this);
	} // end get_class
	
	
	public function get_db_fields()
	{
		return $this->_fields;
	} // end get_db_fields
	
	public function get_db_table()
	{
		return $this->db_table;
	} // end get_db_table
	
	public function get_tabs()
	{
		return $this->_tabs;
	} // end get_tabs
	
	public function __get($field)
	{
		if(isset($this->_fields[$field]))
		{
			return $this->_fields[$field]->get_value();
		}
	} // end __get
	
	
	/**
	 * initialize_db_fields:
	 * Merge any new DB fields into the _fields array. This has the effect of allowing us
	 * to initialize some fields in the parent, but override them in the children.
	 * 
	 * @return null
	 * @param object $db_fields
	 */
	protected function initialize_db_fields($db_fields)
	{
		$this->_fields = array_merge($this->_fields, $db_fields);
	} // end initialize_db_field
	
	
	protected function add_tab($tab)
	{
		if(!isset($this->_tabs[$tab]) || !is_array($this->_tabs[$tab]))
		{
			$this->_tabs[$tab] = array();
		}
	} // end add_tab
	
	protected function add_field_to_tab($tab, $field, $position='after', $anchor_element=false)
	{
		if($anchor_element !== false)
		{
			// Locate the anchor element
			foreach($this->_tabs[$tab] as $key => $value)
			{
				if($value === $anchor_element)
				{
					// Now $key is set right
					break;
				}
			}
			
			// If we are inserting after, we need to add one
			if($position === 'after')
			{
				$key++;
			}
			
			// Now split the array and insert the new element
			$first = array_slice($this->_tabs[$tab], 0, $key); // 0-X
			$middle = array($field);
			$last = array_slice($this->_tabs[$tab], $key); // X-end
			
			// Now merge them back together
			$this->_tabs[$tab] = array_merge($first, $middle, $last);
		}
		else
		{
			// Just add it to the end
			$this->_tabs[$tab][] = $field;
		}
	} // end add_field_to_tab
	
	
	/**
	 * check_table_exists:
	 * Determine if the table exists in the DB.
	 * 
	 * @return bool
	 */
	public function check_table_exists()
	{
		return $this->db->table_exists($this->db_table);
	} // end check_table_exists
	
	
	/**
	 * check_table_up_to_date:
	 * Determine if the model difinition is consistent with the table in the DB.
	 * 
	 * TODO: Add indexes.
	 * TODO: Drop old indexes (i.e. moving primary to other field).
	 * 
	 * @return bool
	 */
	public function check_table_up_to_date($update = false)
	{
		// See if we need to load the DBForge helper
		if($update)
		{
			// Load DBForge helper
			$this->load->dbforge();
			
			if(method_exists($this, 'on_before_update'))
			{
				$this->on_before_update();
			}
		}
		
		
		// Set default return value - assume it's all going to work
		$return = true;
		
		// Get list of fields from the DB
		$fields_from_db = $this->get_db_table_fields();
		//print_r($fields);
		
		foreach($this->_fields as $field_name => $field)
		{
			//echo($field_name);
			if(array_key_exists($field_name, $fields_from_db))
			{
				// Table exists, but is the definition up to date?
				$field_properties = $field->get_properties(true);
				$field_properties_from_db = $fields_from_db[$field_name];
				
				/*
				 * Diff = what exists in A that is not in B, or what is different
				 * between A and B. So, we should be able to take the fields array from the
				 * table definition and diff it against the field definition from the DB.
				 * If things are in the DB from previous iterations, we don't care. We leave
				 * it to the user to log in and delete, or not, as they see fit.
				 */
				$diff = array_diff_assoc($field_properties, $field_properties_from_db);
				
				if(count($diff) && $update)
				{
					// Do the update
					$column = array(
						$field_name => array_merge(
							array(
								'name' => $field_name
							),
							$field->get_field_definition()
						)
					);
					$this->dbforge->modify_column($this->db_table, $column);
					
					if($field->is_primary())
					{
						$this->dbforge->add_key($field_name, true);
					}
					elseif($field->is_indexed())
					{
						$this->dbforge->add_key($field_name);
					}
					
				}
				elseif(count($diff))
				{
					$return = false;
					break; // No point in keeping going
				}
				
			}
			else
			{
				// This field doesn not exist
				if($update)
				{
					// Add field
					$column = array(
						$field_name => $field->get_field_definition()
					);
					$this->dbforge->add_column($this->db_table, $column);
				}
				else
				{
					$return = false;
					break; // No point in keeping going
				}
			}
		}
		
		if($update)
		{
			if(method_exists($this, 'on_after_update'))
			{
				$this->on_after_update();
			}
		}
		
		return $return;
	} // end check_table_up_to_date
	
	
	/**
	 * get_db_table_fields:
	 * Ask the DB for it's list of fields.
	 * 
	 * TODO: current_timestamp
	 * TODO: unique key
	 * TODO: support float types
	 * 
	 * @return object Query Result
	 */
	protected function get_db_table_fields()
	{
		$fields_query = $this->db->query('SHOW FULL COLUMNS FROM ' . $this->db->protect_identifiers($this->db->dbprefix . $this->db_table, true));
		$fields = array();
		
		foreach ($fields_query->result() as $field)
		{
			// Set up the return array with some defaults
			$fields[$field->Field] = array(
				'unsigned' => false,
				'data_length' => '',
				'data_decimals_length' => '',
				'data_values' => ''
			);
			
			/* Extract some data from the field type
			 * Possible formats:
			 * 		text
			 * 		varchar(100)
			 * 		int(10) unsigned
			 * 		enum('Stage','Live','History')
			 */
			
			if(substr_count($field->Type, ')'))
			{
				// There's more data we can extract
				$field_array1 = explode(')', $field->Type);
				
				$field_array2 = explode('(', $field_array1[0]);
				
				$data_type = trim(strtoupper($field_array2[0])); // Handy short cut
				$fields[$field->Field]['data_type'] = $data_type;
				
				// Enums
				if($data_type === 'ENUM')
				{
					$fields[$field->Field]['data_values'] = $field_array2[1];
				}
				else
				{
					$fields[$field->Field]['data_length'] = $field_array2[1];
				}
				
				// Unsigned?
				if($data_type === 'INT' && trim($field_array1[1]) === 'unsigned')
				{
					$fields[$field->Field]['unsigned'] = true;
				}
			}
			else
			{
				// It's just the filed type
				$fields[$field->Field]['data_type'] = trim(strtoupper($field->Type));
			}
			
			$fields[$field->Field]['default_value'] = $field->Default;
			$fields[$field->Field]['allow_null'] = trim($field->Null) === 'YES' ? true : false;
			$fields[$field->Field]['auto_increment'] = trim($field->Extra) === 'auto_increment' ? true : false;
			$fields[$field->Field]['unique_key'] = false;
			$fields[$field->Field]['primary_key'] = (strlen(trim($field->Key)) && trim($field->Key) === 'PRI') ? true : false;
			
			$fields[$field->Field]['current_timestamp'] = '';
			$fields[$field->Field]['index'] = (strlen(trim($field->Key)) && trim($field->Key) !== 'PRI') ? true : false;
			$fields[$field->Field]['comments'] = trim($field->Comment);
		}
		
		return $fields;
	} // end get_db_table_fields
	
	
	/**
	 * create_table:
	 * Create the table, with indexes, in the DB
	 * 
	 * @return null
	 */
	public function create_table()
	{
		// Load DBForge helper
		$this->load->dbforge();
		
		if(method_exists($this, 'on_before_create'))
		{
			$this->on_before_create();
		}
		
		// Plug in each one of the fields
		foreach($this->_fields as $field_name => $field)
		{
			$this->dbforge->add_field(
				array(
					$field_name => $field->get_field_definition()
				)
			);
			//echo("\n<br />$field_name:\n" . print_r($field->get_field_definition(), true) . "\n<br />");
			
			if($field->is_primary())
			{
				$this->dbforge->add_key($field_name, true);
			}
			elseif($field->is_indexed())
			{
				$this->dbforge->add_key($field_name);
			}
		}
		
		// Create the table
		$this->dbforge->create_table($this->db_table);
		
		if(method_exists($this, 'on_after_create'))
		{
			$this->on_after_create();
		}
		
	} // end create_table
	
	
	public function update_table()
	{
		// Just call check_table_up_to_date, telling it to go ahead and update the table if not.
		$this->check_table_up_to_date(true);
	} // end update_table
	
	
} // end PHLARE_Model